Skip to content

fix(identity): one delegation-chain rule for demand-side grants - #1817

Merged
bokelley merged 2 commits into
nextfrom
bokelley/delegation-chain-rule-8473
Nov 2, 2026
Merged

bokelley merged 2 commits into
nextfrom
bokelley/delegation-chain-rule-8473

Conversation

@bokelley

@bokelley bokelley commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Ports the spec erratum adcontextprotocol/adcp#8473 (one delegation-chain rule for demand-side grants, erratum to ratified R6) to TrustGrantEvaluator, and vendors its cross-SDK vectors. Mirrors adcontextprotocol/adcp-client#3452.

Rule changes (src/adcp/signing/trust.py)

  • Implicit authority applies when account.operator and the signer's operator are each the brand or its house, in any combination. The new cases are a brand account with the house as signer, and a house account with the brand as signer.
  • One chain rule. The rule allows at most two grants from the house's record to the signer's operator, and every grant must cover the claim. account.operator must be the brand, the house, or the first grant's grantee. Every covering first grant is a candidate, where before only the grant to account.operator was. Distinct intermediary records are read concurrently, at most 8 at a time. An unreadable intermediary counts as an outage (SERVICE_UNAVAILABLE) only when no other chain authorizes.
  • Absence. exclusive_grants: true on the house's record now makes grants[] exhaustive whatever the scope. Before, it applied only to the scopes some grant named. Without the flag, an uncovered claim goes to seller_authorization.
  • New GrantAuthorization.source ("implicit", "trust", "seller" or "legacy"; None when denied), matching result.source in TS.

Behavior changes for adopters

  • An exclusive_grants record whose grants name only other scopes now denies with grant_absent instead of deferring to seller_authorization.
  • A house agent acting under its child brand's self-operated account is now implicit. test_an_operator_outside_the_brand_and_its_house_still_needs_a_grant is updated to match.

Vectors

tests/fixtures/delegation-chains/delegation-chains.json holds all 48 vectors from static/test-vectors/delegation-chains.json at adcp merge commit d397b8ea (the #8473 merge, unchanged on main since). PINNED.json records the commit and content sha256, and a test checks the hash. The content is identical to the TS SDK's vendored copy, which differs only in formatting. Against the previous implementation, 26 of 48 vectors fail.

Tests

  • tests/test_delegation_chain_vectors.py: one test per vector, plus checks for outcome coverage and the pinned hash.
  • tests/test_experimental_identity.py: an unreadable intermediary next to an authorizing chain, the outage-only case, and exclusive_grants regardless of scope.

🤖 Generated with Claude Code

bokelley and others added 2 commits November 2, 2026 18:20
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports adcontextprotocol/adcp#8473 to TrustGrantEvaluator: implicit
authority when account.operator and the signer's operator are each the
brand or its house; at most two grants from the house's record, every
covering first grant a candidate, account.operator the brand, house or
first grantee; an unreadable intermediary is an outage only when no
chain authorizes; exclusive_grants is exhaustive whatever the scope.
Adds GrantAuthorization.source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bokelley
bokelley marked this pull request as ready for review November 2, 2026 18:33

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — clean port of adcp#8473 (single delegation-chain rule for demand-side grants) into TrustGrantEvaluator.

What I checked:

  • trust.py delegation logic (~84 net lines): first_links() groups covering first grants by registrable grantee under the account.operator-on-chain constraint; concurrent intermediary fetch bounded by Semaphore(8); firsts recomputed at post-fetch decision time; outage consulted only when no chain authorizes (fail-closed to SERVICE_UNAVAILABLE); exclusive_grants now exhaustive across scopes.
  • Public-API/semver: source is an additive optional field; fix: prefix is correct and the behavior shift is documented in the PR body and migration doc. No public-export removal/rename/required→optional flip, so no feat!:/BREAKING CHANGE: needed.
  • Tests: 48 hash-pinned vendored delegation-chain vectors with full outcome coverage plus three targeted unit tests. CI gates stay armed — no disabled tests, no blanket ignores.

Risk assessment: high_risk is true only because src/adcp/signing/trust.py matches src/adcp/signing/** as a (modified) file. Per the risk rules, a modification with no medium-or-higher finding is presumed safe — not an escalation trigger on the flag alone. gated_paths is false, no author-team gate, no prior decision.

No blocking or medium findings. Decision table rows 1–8 do not fire; falls through to row 9.

@bokelley
bokelley merged commit d7ef70b into next Nov 2, 2026
42 checks passed
@bokelley
bokelley deleted the bokelley/delegation-chain-rule-8473 branch November 2, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant